﻿using System.Reflection;
using K9Nano.Extensions;
using Microsoft.Extensions.DependencyInjection;

namespace K9Nano.Dependency;

public static class AssemblyRegisterExtensions
{
    public static IServiceCollection RegisterByConvention(this IServiceCollection services, Assembly? assembly = null)
    {
        assembly ??= Assembly.GetCallingAssembly();

        var classes = assembly.DefinedTypes
            .Where(x =>
                x is { IsClass: true, IsAbstract: false }
                && x.GetInterfaces()
                    .Any(i => i.Name is nameof(ISingletonDependency) or nameof(ITransientDependency) or nameof(IScopedDependency)))
            .Select(x => x.AsType());

        foreach (var implType in classes)
        {
            var name = implType.Name;

            if (implType.IsGenericTypeDefinition)
            {
                throw new Exception($"Cannot register {implType} for conventional register does not support generic type. Please register it manually.");
            }

            var baseTypes = GetServiceTypes(implType);

            if (implType.TryGetCustomAttribute<ServiceKeyNameAttribute>(false, out var key))
            {
                Func<Type, string, Type, IServiceCollection> registerFunc;
                if (typeof(ITransientDependency).IsAssignableFrom(implType))
                {
                    registerFunc = services.AddKeyedTransient;
                }
                else if (typeof(IScopedDependency).IsAssignableFrom(implType))
                {
                    registerFunc = services.AddKeyedScoped;
                }
                else
                {
                    registerFunc = services.AddKeyedSingleton;
                }

                foreach (var baseType in baseTypes)
                {
                    registerFunc(baseType, key!.Key, implType);
                }
            }
            else
            {

                Func<Type, Type, IServiceCollection> registerFunc;
                if (typeof(ITransientDependency).IsAssignableFrom(implType))
                {
                    registerFunc = services.AddTransient;
                }
                else if (typeof(IScopedDependency).IsAssignableFrom(implType))
                {
                    registerFunc = services.AddScoped;
                }
                else
                {
                    registerFunc = services.AddSingleton;
                }

                foreach (var baseType in baseTypes)
                {
                    registerFunc(baseType, implType);
                }
            }
        }

        return services;
    }

    private static Type[] GetServiceTypes(Type implementationType)
    {
        return implementationType.GetInterfaces()
            .Where(x =>
            {
                var isMatch = implementationType.Name.EndsWith(GetConventionalName(x.Name), StringComparison.Ordinal);
                return isMatch;
            })
            .ToArray();
    }

    private static string GetConventionalName(string interfaceName)
    {
        return interfaceName.StartsWith('I') ? interfaceName[1..] : interfaceName;
    }
}